#include <iterator>
#include <unistd.h>
#include "MarkupSTL.h"
#include "global.h"
#include "Game.h"
#include "RedisClient.h"
#include "Logic.h"
#include "json.h"
#include "globalExtern.h"
#include "Host.h"
#include "NodeServer.h"
#include "Md5.h"
#include "2200_game_userinfo.pb.h"

CGameServer::CGameServer():m_timerflag(false),m_procIndex(0),m_procCount(0)
{
	m_moneyRedis = NULL;
}

CGameServer::~CGameServer()
{
}


bool CGameServer::InitGame()
{
	return true;
}

int CGameServer::OnBYTimeOut(int Timerid)
{
	switch (Timerid)
	{
	case TIMERID_LOCALCACHE_CHECK:
	{
		procLocalCacheCheck();
		break;
	}
	default:
		break;
	}
	return 0;
}

int CGameServer::ProcEventGameInit(int ev_id, SpiderEvent* pEvent)
{
	if (m_timerflag)
		return 0;

	m_cache.set_begin_callback([this](){
		log_debug("temp begin!!! 第一个用户加入，开始监控");
		this->StartTimer(TIMERID_LOCALCACHE_CHECK, g_localcache_check_dur, true);
	});
	m_cache.set_end_callback([this](){
		log_debug("temp end!!! 所有用户已过期/移除，停止监控");
		this->StopTimer(TIMERID_LOCALCACHE_CHECK);
	});
	return 0;
}

int CGameServer::ProcEventPublishService(int ev_id, SpiderEvent* pEvent)
{
	if (!(pEvent && pEvent->evParam.ptr))
		return -1;

	ServiceConf* pserviceConf = (ServiceConf*)pEvent->evParam.ptr;
	if (SERVER_TYPE_GAME_USERINFO == pserviceConf->server_type)
	{
		ServiceTypeList::const_iterator itor = m_pHost->m_pNode->m_serviceTypeList.find(SERVER_TYPE_GAME_USERINFO);
		if (m_pHost->m_pNode->m_serviceTypeList.end() != itor && itor->second.size() > 0)
		{
			m_procIndex = 0;
			m_procCount = 0;
			int index = 0;
			for (ServiceVect::const_iterator itor1 = itor->second.begin(); itor->second.end() != itor1; ++itor1, ++index)
			{
				if (TGlobal::_svid == itor1->svid)
				{
					break;
				}
			}

			if (index < (int)itor->second.size())
			{
				m_procIndex = index;
				m_procCount = itor->second.size();
			}
			else
			{
				log_error("service find no srv id,srv id,%d", TGlobal::_svid);
			}

			log_boot("service pulish ready, srv id,%d,index,%u,count,%u", TGlobal::_svid, m_procIndex, m_procCount);
		}
		else
		{
			log_error("service pulish not ready");
		}
	}
	return 0;
}

int CGameServer::ProcessPacket(SocketHandler* pHandler, NETInputPacket* inputPkg)
{
	unsigned int cmd = inputPkg->GetCmdType();
	switch (cmd)
	{
	case game_userinfo::GAME_USERINFO_CMD_GET_USERINFO_BYFIELD_REQ:
	{
		ProcGetUserinfoByField(pHandler, inputPkg);
		break;
	}
	case game_userinfo::GAME_USERINFO_CMD_UPDATE_USERINFO_REQ:
	{
		ProcUpdateUserinfo(pHandler, inputPkg);
		break;
	}
	default:
		log_error("not find cmd = %u", cmd);
		break;
	}

	return 0;
}

int CGameServer::ProcUpdateUserinfo(SocketHandler* pHandler, NETInputPacket* inputPkg)
{
	game_userinfo::updateUserinfoResp rsp;
	game_userinfo::updateUserinfoReq msg;
	bool paret = inputPkg->parsePb(&msg);
	unsigned int uid = 0;
	do
	{
		if (!paret)
		{
			log_error("game_userinfo::getUserinfoReq parsePb error!");
			rsp.set_result(game_userinfo::ParsePbErr);
			break;
		}
		log_debug("req:%s", msg.ShortDebugString().c_str());
		uid = msg.uid();
		if (0 == uid)
		{
			log_error("game_userinfo::getUserinfoReq uid = 0!");
			rsp.set_result(game_userinfo::UidZero);
			break;
		}

		rsp.set_uid(uid);

		unsigned int game_id = msg.game_id();
		if (0 == game_id)
		{
			log_error("game_userinfo::getUserinfoReq game_id = 0!");
			rsp.set_result(game_userinfo::GameIdZero);
			break;
		}

		if (0 == m_procCount)
		{
			rsp.set_result(game_userinfo::ServerErr);
			log_error("not ready,svid,%d,srcsrvtype,%hu,srcsrvid,%u"
				,TGlobal::_svid, pHandler->getType(), pHandler->getId());
			break;
		}
		
		if (m_procIndex != uid%m_procCount)
		{
			rsp.set_result(game_userinfo::SendErr);
			log_error("uid mod not match,svid,%d,index,%uproccount,%u,srcsrvtype,%hu,srcsrvid,%u"
				,TGlobal::_svid, m_procIndex, m_procCount, pHandler->getType(), pHandler->getId());
			break;
		}

		stUserinfo* pUserInfo = getUserinfoPtr(uid, game_id);
		if (pUserInfo == nullptr)
		{
			rsp.set_result(game_userinfo::GetUserInfoErr);
			break;
		}

		rsp.set_result(game_userinfo::Success);
		for (int i = 0; i < msg.field_value_list_size(); i++)
		{
			int field = msg.field_value_list(i).field();
			if (field == game_userinfo::FIELD_HEADER)
			{
				pUserInfo->header = msg.field_value_list(i).str_val();
				log_debug("new head = %s", pUserInfo->header.c_str());
			}
			else if (field == game_userinfo::FIELD_NICKNAME)
			{
				pUserInfo->nickname = msg.field_value_list(i).str_val();
				log_debug("new nickname = %s", pUserInfo->nickname.c_str());
			}
			else if (field == game_userinfo::FIELD_GENDER)
			{
				pUserInfo->gender = msg.field_value_list(i).int_val();
				log_debug("new gender = %d", pUserInfo->gender);
			}
		}

		std::uint64_t key = getKey(uid, game_id);
		auto itor = m_cache.get(key);
		if (itor != nullptr)
		{
			log_debug("header = %s", itor->header.c_str());
		}

		updateCacheAll(uid, game_id, pUserInfo);

	}while (false);

	if (!msg.trans().empty())
	{
		rsp.set_trans(msg.trans());
	}
	log_debug("rsp:%s", rsp.ShortDebugString().c_str());
	if (SERVER_TYPE_AGENT == pHandler->getType())//客户端加密消息
	{
		if (0 == uid)
			uid = inputPkg->GetSrcSvid();

		if (uid > 0)
			m_pHost->SendPlayerPb(pHandler,  game_userinfo::GAME_USERINFO_CMD_UPDATE_USERINFO_RESP, uid, &rsp);
	}
	else
	{
		m_pHost->SendPb(pHandler, game_userinfo::GAME_USERINFO_CMD_UPDATE_USERINFO_RESP, pHandler->getType(), pHandler->getId(), &rsp);
	}
	return 0;
}

int CGameServer::ProcGetUserinfoByField(SocketHandler* pHandler, NETInputPacket* inputPkg)
{
	game_userinfo::getUserinfoByfieldResp rsp;
	game_userinfo::getUserinfoByfieldReq msg;
	bool paret = inputPkg->parsePb(&msg);
	unsigned int uid = 0;
	unsigned int game_id = 0;
	do 
	{
		if (!paret)
		{
			log_error("game_userinfo::getUserinfoReq parsePb error!");
			rsp.set_result(game_userinfo::ParsePbErr);
			break;
		}
		log_debug("req:%s", msg.ShortDebugString().c_str());
		uid = msg.uid();
		if (0 == uid)
		{
			log_error("game_userinfo::getUserinfoReq uid = 0!");
			rsp.set_result(game_userinfo::UidZero);
			break;
		}

		rsp.set_uid(uid);

		game_id = msg.game_id();
		if (0 == game_id)
		{
			log_error("game_userinfo::getUserinfoReq game_id = 0!");
			rsp.set_result(game_userinfo::GameIdZero);
			break;
		}

		if (0 == m_procCount)
		{
			rsp.set_result(game_userinfo::ServerErr);
			log_error("not ready,svid,%d,srcsrvtype,%hu,srcsrvid,%u"
				,TGlobal::_svid, pHandler->getType(), pHandler->getId());
			break;
		}
		
		if (m_procIndex != uid%m_procCount)
		{
			rsp.set_result(game_userinfo::SendErr);
			log_error("uid mod not match,svid,%d,index,%uproccount,%u,srcsrvtype,%hu,srcsrvid,%u"
				,TGlobal::_svid, m_procIndex, m_procCount, pHandler->getType(), pHandler->getId());
			break;
		}
		
		stUserinfo* pUserInfo = getUserinfoPtr(uid, game_id);
		if (pUserInfo == nullptr)
		{
			rsp.set_result(game_userinfo::GetUserInfoErr);
			break;
		}

		rsp.set_result(game_userinfo::Success);
		if (msg.field_list_size() == 0)
		{
			auto data = rsp.add_field_value_list();
			data->set_field(game_userinfo::FIELD_HEADER);
			data->set_str_val(pUserInfo->header);

			auto data1 = rsp.add_field_value_list();
			data1->set_field(game_userinfo::FIELD_NICKNAME);
			data1->set_str_val(pUserInfo->nickname);

			auto data2 = rsp.add_field_value_list();
			data2->set_field(game_userinfo::FIELD_GENDER);
			data2->set_int_val(pUserInfo->gender);
			break;
		}
		
		for (int i = 0; i < msg.field_list_size(); i++)
		{
			game_userinfo::USER_FIELD field = msg.field_list(i);
			auto data = rsp.add_field_value_list();
			data->set_field(field);
			if (field == game_userinfo::FIELD_HEADER)
			{
				data->set_str_val(pUserInfo->header);
			}
			else if (field == game_userinfo::FIELD_NICKNAME)
			{
				data->set_str_val(pUserInfo->nickname);
			}
			else if (field == game_userinfo::FIELD_GENDER)
			{
				data->set_int_val(pUserInfo->gender);
			}
		}

	} while (false);

	if (!msg.trans().empty())
	{
		rsp.set_trans(msg.trans());
	}

	if (SERVER_TYPE_AGENT == pHandler->getType())//客户端加密消息
	{
		if (0 == uid)
			uid = inputPkg->GetSrcSvid();

		if (uid > 0)
			m_pHost->SendPlayerPb(pHandler,  game_userinfo::GAME_USERINFO_CMD_GET_USERINFO_BYFIELD_RESP, uid, &rsp);
	}
	else
	{
		m_pHost->SendPb(pHandler, game_userinfo::GAME_USERINFO_CMD_GET_USERINFO_BYFIELD_RESP, pHandler->getType(), pHandler->getId(), &rsp);
	}
	return 0;
}

std::uint64_t CGameServer::getKey(const unsigned int uid, const unsigned int game_id)
{
	return ((std::uint64_t)game_id << 32) + uid;
}

stUserinfo* CGameServer::getUserinfoPtr(const unsigned int uid, const unsigned int game_id)
{
	stUserinfo* ptr = nullptr;
	do
	{
		//1.内存中找
		ptr = getUserinfoAllFromLocalCache(uid, game_id);
		if (ptr != nullptr)
		{
			break;
		}
		//2.redis中找
		ptr = getUserinfoAllFromCache(uid, game_id);
		if (ptr != nullptr)
		{
			updateLocalCacheAll(uid, game_id, ptr); // 更新内存
			break;
		}
		//3.mysql中找
		ptr = getUserinfoAllFromDB(uid, game_id);
		if (ptr != nullptr)
		{
			//mysql中找到了，更新redis和内存
			updateAllCache(uid, game_id, ptr);
			break;
		}
	} while (false);
	if (ptr == nullptr)
	{
		ptr = new stUserinfo();
		ptr->nickname = "Player" + std::to_string(uid);
		//默认数据，只用更新内存就行
		updateLocalCacheAll(uid, game_id, ptr);
	}

	return ptr;
}

stUserinfo* CGameServer::getUserinfoAllFromLocalCache(const unsigned int uid, const unsigned int game_id)
{
	std::uint64_t key = getKey(uid, game_id);
	stUserinfo* pUserinfo = m_cache.get(key);
	if (pUserinfo == nullptr)
	{
		return nullptr;
	}
	log_debug("cache find! uid = %u, game_id = %u header = %s", uid, game_id, pUserinfo->header.c_str());
	return pUserinfo;
}

stUserinfo* CGameServer::getUserinfoAllFromDB(const unsigned int uid, const unsigned int game_id)
{
	log_debug("db tofind uid = %u, game_id = %u", uid, game_id);
	return m_dbHandler.getUserinfoByGame(uid, game_id);
}

void CGameServer::setUserinfoFromRedis(game_userinfo::userinfoDataDb *data, stUserinfo* pUserInfo)
{
	for (int i = 0; i < data->field_value_list_size(); i++)
	{
		game_userinfo::USER_FIELD field = data->field_value_list(i).field();
		if (field == game_userinfo::FIELD_HEADER)
		{
			pUserInfo->header = data->field_value_list(i).str_val();
		}
		else if (field == game_userinfo::FIELD_NICKNAME)
		{
			pUserInfo->nickname = data->field_value_list(i).str_val();
		}
		else if (field == game_userinfo::FIELD_GENDER)
		{
			pUserInfo->gender = data->field_value_list(i).int_val();
		}
	}
}

void CGameServer::setUserinfoToRedis(game_userinfo::userinfoDataDb *data, const stUserinfo* pUserInfo)
{
	auto data0 = data->add_field_value_list();
	data0->set_field(game_userinfo::FIELD_HEADER);
	data0->set_str_val(pUserInfo->header);

	auto data1 = data->add_field_value_list();
	data1->set_field(game_userinfo::FIELD_NICKNAME);
	data1->set_str_val(pUserInfo->nickname);

	auto data2 = data->add_field_value_list();
	data2->set_field(game_userinfo::FIELD_GENDER);
	data2->set_int_val(pUserInfo->gender);
}

stUserinfo* CGameServer::getUserinfoAllFromCache(const unsigned int uid, const unsigned int game_id)
{
	std::string redisKey = "game_userinfo:" + std::to_string(uid);
	std::string resVal;
	m_moneyRedis->Hget(redisKey, std::to_string(game_id), &resVal);
	stUserinfo* pUserInfo = new stUserinfo();
	if (resVal.size() > 0)
	{
		game_userinfo::userinfoDataDb data;
		if (!data.ParseFromString(resVal))
		{
			log_error("ParseFromString err! uid = %u, game_id = %u", uid, game_id);
			delete pUserInfo;
			return nullptr;
		}
		setUserinfoFromRedis(&data, pUserInfo);
	}
	else
	{
		delete pUserInfo;
		return nullptr;
	}

	log_debug("get Userinfo all from redis,uid,%u game_id = %d", uid, game_id);
	return pUserInfo;
}

bool CGameServer::updateAllCache(const unsigned int uid, const unsigned int game_id, stUserinfo* pUserInfo)
{
	game_userinfo::userinfoDataDb data;
	setUserinfoToRedis(&data, pUserInfo);
    std::string info;
	data.SerializeToString(&info);

	std::string redisKey = "game_userinfo:" + std::to_string(uid);
	m_moneyRedis->Hset(redisKey, std::to_string(game_id), info);
	log_debug("hset %s %d", redisKey.c_str(), game_id);

	m_moneyRedis->Expire(redisKey, g_cache_cool_down_dur);

	updateLocalCacheAll(uid, game_id, pUserInfo);
	return true;
}

bool CGameServer::updateLocalCacheAll(const unsigned int uid, const unsigned int game_id, stUserinfo* pUserInfo)
{
	std::uint64_t key = getKey(uid, game_id);
	m_cache.insert(key, pUserInfo);

	log_debug("uid = %u, game_id = %u header = %s", uid, game_id, pUserInfo->header.c_str());
	return true;
}

bool CGameServer::updateCacheAll(const unsigned int uid, const unsigned int game_id, const stUserinfo* pUserInfo)
{
	game_userinfo::userinfoDataDb data;
	setUserinfoToRedis(&data, pUserInfo);
    std::string info;
	data.SerializeToString(&info);

	std::string redisKey = "game_userinfo:" + std::to_string(uid);
	m_moneyRedis->Hset(redisKey, std::to_string(game_id), info);
	log_debug("hset %s %d", redisKey.c_str(), game_id);

	m_moneyRedis->Expire(redisKey, g_cache_cool_down_dur);

	return updateToDb(uid);
}

bool CGameServer::updateToDb(const unsigned int uid)
{
	ostringstream ossKey;
	int index = uid % MOD_DENOMINATOR;
	ossKey << "game_userinfo_upuid_set:" << index;
	ostringstream ossVal;
	ossVal << uid;
	m_moneyRedis->Sadd(ossKey.str(), ossVal.str());

	log_debug("sadd,%s, val,%s", ossKey.str().c_str(), ossVal.str().c_str());
	return true;
}

int CGameServer::procLocalCacheCheck(void)
{
	log_info("temp!! before size = %d",(int)m_cache.size());
	m_cache.purge_expired();
	log_info("temp!! after size = %d",(int)m_cache.size());
	return 0;
}